package cn.jh.common.core.cn;

import cn.jh.common.core.json.JsonKt;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.collect.ImmutableList;
import org.inferred.freebuilder.FreeBuilder;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Pattern;

/**
 * @author Fuchun
 * @since 1.0
 */
public class Cards {

    private static final String SOURCE_PATH = "/META-INF/data/bank_card_sources_bin.json";
    private static final Companion COMPANION = new Companion();

    @Nullable
    public static CardInfo find(String cardNo) {
        return COMPANION.matchAndGet(cardNo);
    }

    private static class Companion {

        private final List<CardSource> resources;

        private Companion() {
            resources = initResources();
        }

        @SuppressWarnings("unchecked")
        private List<CardSource> initResources() {
            List<Map<String, Object>> list = readListMap();
            List<CardSource> csList = new ArrayList<>(list.size());
            for (Map<String, Object> objMap : list) {
                String bankCode = (String) objMap.get("bankCode");
                String bankName = (String) objMap.get("bankName");
                CardType cardType;
                Pattern pattern;
                CardPattern cardPattern;
                List<Map<String, String>> patternList = (List<Map<String, String>>) objMap.get("patterns");
                List<CardPattern> patterns = new ArrayList<>(patternList.size());
                for (Map<String, String> patternMap : patternList) {
                    cardType = CardType.forString(patternMap.get("cardType"));
                    pattern = Pattern.compile(patternMap.get("reg"));
                    cardPattern = new CardPattern.Builder()
                            .setCardType(cardType)
                            .setReg(pattern).build();
                    patterns.add(cardPattern);
                }
                csList.add(new CardSource.Builder()
                        .setBankCode(bankCode)
                        .setBankName(bankName)
                        .addAllPatterns(patterns).build());
            }
            List<CardSource> result = ImmutableList.copyOf(csList);
            list.clear();
            csList.clear();
            return result;
        }

        private List<Map<String, Object>> readListMap() {
            MapType mapType = TypeFactory.defaultInstance()
                    .constructMapType(Map.class, String.class, Object.class);
            CollectionType listType = TypeFactory.defaultInstance()
                    .constructCollectionType(List.class, mapType);
            try (InputStream input = getClass().getResourceAsStream(SOURCE_PATH)) {
                return JsonKt.readValue(input, listType);
            } catch (IOException ex) {
                throw new UncheckedIOException(ex);
            }
        }

        @Nullable CardInfo matchAndGet(String cardNo) {
            for (CardSource source : resources) {
                for (CardPattern cp : source.getPatterns()) {
                    if (cp.getPredicate().test(cardNo)) {
                        return new CardInfo.Builder()
                                .setBankCode(source.getBankCode())
                                .setBankName(source.getBankName())
                                .setCardNo(cardNo)
                                .setCardType(cp.getCardType())
                                .build();
                    }
                }
            }
            return null;
        }

    }

    @FreeBuilder
    @JsonDeserialize(builder = CardPattern.Builder.class)
    public static abstract class CardPattern {

        public abstract Pattern getReg();

        public abstract CardType getCardType();

        public abstract Predicate<String> getPredicate();

        public static class Builder extends Cards_CardPattern_Builder {

            @Override
            public Builder setReg(Pattern reg) {
                Builder builder = super.setReg(reg);
                setPredicate(reg.asPredicate());
                return builder;
            }
        }
    }

    @FreeBuilder
    @JsonDeserialize(builder = CardSource.Builder.class)
    public interface CardSource {

        String getBankName();

        String getBankCode();

        List<CardPattern> getPatterns();

        class Builder extends Cards_CardSource_Builder {
        }
    }
}
